package lowMemoryUsage;

import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.TreeMap;

/*
 * Vecteur TF - IDF ne contenant que la liste de ses composantes non nulles
 * 
 */
public class VectorTI_LM implements Iterable<Integer>, Iterator<Integer> {

    private TreeMap<Integer, Double> values;
    private Integer iteratorValue = 0;

    public VectorTI_LM() {
        values = new TreeMap<Integer, Double>();
    }

    //Constructeur par copie
    public VectorTI_LM(VectorTI_LM v) {
        values = new TreeMap<Integer, Double>();

        if (v != null) {
            for (Integer i : v) {
                this.add(i, v.get(i));
            }
        }
    }

    public Double get(Integer index) {
        if (values.containsKey(index)) {
            return values.get(index);
        } else {
            return 0d;
        }
    }

    public void add(Integer index, Double val) {
        values.put(index, val);
    }

    public Integer size() {
        return values.size();
    }

    /*
     * Clone de java est broken
     * Voir http://stackoverflow.com/questions/2326758/how-to-properly-override-clone-method
     */
//    @Override
//    public VectorTILowMem clone(){
//        VectorTILowMem newClone = new VectorTILowMem();
//        for(Map.Entry<Integer, Double> e : values.entrySet()){
//            newClone.add(e.getKey(), e.getValue());
//        }
//        
//        return newClone;
//    }
    
    public void normalize() {
        double norm = 0f;

        for (Map.Entry<Integer, Double> e : values.entrySet()) {
            norm += e.getValue() * e.getValue();
        }

        norm = Math.sqrt(norm);

        //Diviser chaque valeur par la norme du vecteur
        for (Map.Entry<Integer, Double> e : values.entrySet()) {
            values.put(e.getKey(), values.get(e.getKey()) / norm);
        }
    }

    //Affichage des composantes non nulles sous la forme :
    //indexComposante -> valeur
    @Override
    public String toString() {
        DecimalFormat twoDecimals = new DecimalFormat("#.##");

        String print = "";

        for (Map.Entry<Integer, Double> e : values.entrySet()) {
            print += e.getKey() + " -> " + twoDecimals.format(e.getValue()) + "; ";
        }

        return print;
    }

    //Affichage des composantes non nulles sous la forme :
    //motComposante -> valeur
    public String toString(ArrayList<String> voc) {
        DecimalFormat twoDecimals = new DecimalFormat("#.##");

        String print = "";

        for (Map.Entry<Integer, Double> e : values.entrySet()) {
            print += voc.get(e.getKey()) + " " + twoDecimals.format(e.getValue()) + "; ";
        }

        return print;
    }

    @Override
    public boolean equals(Object other) {
        if (other == null) {
            return false;
        }
        if (other == this) {
            return true;
        }
        if (!(other instanceof VectorTI_LM)) {
            return false;
        }
        VectorTI_LM otherVector = (VectorTI_LM) other;

        if(otherVector.size() != this.size()){
            return false;
        }
        
        for (Map.Entry<Integer, Double> e : values.entrySet()) {
            if (otherVector.get(e.getKey()) != e.getValue()) {
                return false;
            }
        }

        return true;
    }

    @Override
    public int hashCode() {
        int hash = 7;
        hash = 79 * hash + Objects.hashCode(this.values);
        return hash;
    }

    /*
     * Retourne vrai si le vecteur possède l'index "index"
     */
    public Boolean containsIndex(Integer index) {
        for (Integer i : values.keySet()) {
            if (i == index) {
                return true;
            }
        }

        return false;
    }


    /*
     * Voir http://fr.wikipedia.org/wiki/Distance_(math%C3%A9matiques)
     * parametre = 1 -> distance de Manhattan
     * parametre = 2 -> distance Euclidienne
     * parametre = x -> distance de Minkowski
     */
    private static double distanceParametree(VectorTI_LM v1, VectorTI_LM v2, Integer p) {
        double sum = 0d;

        ArrayList<Integer> allIndexes = allDifferentIndexes(v1, v2);

        //Parcours de tout les index
        for (Integer i : allIndexes) {
            //System.out.println("i=" + i);
            //System.out.println("v1.get(i)=" + v1.get(i) + "; " + "v2.get(i)=" + v2.get(i));
            sum += Math.pow(Math.abs(v1.get(i) - v2.get(i)), p);
            //System.out.println("sum="+sum);
        }

        double result = Math.pow(sum, 1d / p);
        //System.out.println("result="+result);

        return result;
    }

    public static double distanceManhattan(VectorTI_LM v1, VectorTI_LM v2) {
        return distanceParametree(v1, v2, 1);
    }

    public static double distanceEuclidienne(VectorTI_LM v1, VectorTI_LM v2) {
        return distanceParametree(v1, v2, 2);
    }

    public static double distanceMinkowski(VectorTI_LM v1, VectorTI_LM v2, Integer parameter) {
        return distanceParametree(v1, v2, parameter);
    }

    @Override
    public Iterator<Integer> iterator() {
        iteratorValue = 0;
        return this;
    }

    @Override
    public boolean hasNext() {
        if (iteratorValue < values.size()) {
            return true;
        }
        return false;
    }

    @Override
    public Integer next() {
        int counter = 0;
        for (Map.Entry<Integer, Double> e : values.entrySet()) {
            if (counter < iteratorValue) {
                counter++;
                continue;
            }

            iteratorValue++;
            return e.getKey();
        }

        throw new NoSuchElementException();
    }

    @Override
    public void remove() {
        throw new UnsupportedOperationException();
    }

    //retourne la liste des tous les index différents dans cette liste de vecteurs
    public static ArrayList<Integer> allDifferentIndexes(ArrayList<VectorTI_LM> vectors) {
        ArrayList<Integer> result = new ArrayList<Integer>();

        for (VectorTI_LM v : vectors) {//chaque vecteur
            for (Integer i : v) {//chaque index
                if (!result.contains(i)) {
                    result.add(i);
                }
            }
        }
        //System.out.println("result :" + result.size());
        return result;
    }

    //retourne la liste des tous les index différents des deux vecteurs
    public static ArrayList<Integer> allDifferentIndexes(VectorTI_LM v1, VectorTI_LM v2) {
        ArrayList<VectorTI_LM> vectors = new ArrayList<VectorTI_LM>();
        vectors.add(v1);
        vectors.add(v2);

        return allDifferentIndexes(vectors);
    }
    
    
    //Retourne la liste des index en commun entre tous les vecteurs en entrée
//    public static ArrayList<Integer> indexesInCommon(ArrayList<VectorTILowMem> vectors){
//        if(vectors.size() == 0) return null;
//        
//        ArrayList<Integer> indexInCommon = new ArrayList<Integer>();
//        Boolean currentIndexIsInCommon;
//        
//        //parcours de l'index du premier vecteur de la liste
//        for(Integer index : vectors.get(0)){
//            currentIndexIsInCommon = true;
//            
//            //parcours des autres vecteur
//            for(int i=1; i<vectors.size(); i++){
//                if(!vectors.get(i).containsIndex(index)){
//                    currentIndexIsInCommon = false;
//                }
//            }
//            
//            if(currentIndexIsInCommon){
//                indexInCommon.add(index);
//            }
//        }
//        
//        return indexInCommon;
//    }
}
